home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / perl / 5.10.0 / CPANPLUS / Internals / Source.pm < prev    next >
Encoding:
Perl POD Document  |  2009-06-26  |  42.8 KB  |  1,513 lines

  1. package CPANPLUS::Internals::Source;
  2.  
  3. use strict;
  4.  
  5. use CPANPLUS::Error;
  6. use CPANPLUS::Module;
  7. use CPANPLUS::Module::Fake;
  8. use CPANPLUS::Module::Author;
  9. use CPANPLUS::Internals::Constants;
  10.  
  11. use File::Fetch;
  12. use Archive::Extract;
  13.  
  14. use IPC::Cmd                    qw[can_run];
  15. use File::Temp                  qw[tempdir];
  16. use File::Basename              qw[dirname];
  17. use Params::Check               qw[check];
  18. use Module::Load::Conditional   qw[can_load];
  19. use Locale::Maketext::Simple    Class => 'CPANPLUS', Style => 'gettext';
  20.  
  21. $Params::Check::VERBOSE = 1;
  22.  
  23. =pod
  24.  
  25. =head1 NAME
  26.  
  27. CPANPLUS::Internals::Source
  28.  
  29. =head1 SYNOPSIS
  30.  
  31.     ### lazy load author/module trees ###
  32.  
  33.     $cb->_author_tree;
  34.     $cb->_module_tree;
  35.  
  36. =head1 DESCRIPTION
  37.  
  38. CPANPLUS::Internals::Source controls the updating of source files and
  39. the parsing of them into usable module/author trees to be used by
  40. C<CPANPLUS>.
  41.  
  42. Functions exist to check if source files are still C<good to use> as
  43. well as update them, and then parse them.
  44.  
  45. The flow looks like this:
  46.  
  47.     $cb->_author_tree || $cb->_module_tree
  48.         $cb->_check_trees
  49.             $cb->__check_uptodate
  50.                 $cb->_update_source
  51.             $cb->__update_custom_module_sources 
  52.                 $cb->__update_custom_module_source
  53.         $cb->_build_trees
  54.             $cb->__create_author_tree
  55.                 $cb->__retrieve_source
  56.             $cb->__create_module_tree
  57.                 $cb->__retrieve_source
  58.                 $cb->__create_dslip_tree
  59.                     $cb->__retrieve_source
  60.             $cb->__create_custom_module_entries                    
  61.             $cb->_save_source
  62.  
  63.     $cb->_dslip_defs
  64.  
  65. =head1 METHODS
  66.  
  67. =cut
  68.  
  69. {
  70.     my $recurse; # flag to prevent recursive calls to *_tree functions
  71.  
  72.     ### lazy loading of module tree
  73.     sub _module_tree {
  74.         my $self = $_[0];
  75.  
  76.         unless ($self->{_modtree} or $recurse++ > 0) {
  77.             my $uptodate = $self->_check_trees( @_[1..$#_] );
  78.             $self->_build_trees(uptodate => $uptodate);
  79.         }
  80.  
  81.         $recurse--;
  82.         return $self->{_modtree};
  83.     }
  84.  
  85.     ### lazy loading of author tree
  86.     sub _author_tree {
  87.         my $self = $_[0];
  88.  
  89.         unless ($self->{_authortree} or $recurse++ > 0) {
  90.             my $uptodate = $self->_check_trees( @_[1..$#_] );
  91.             $self->_build_trees(uptodate => $uptodate);
  92.         }
  93.  
  94.         $recurse--;
  95.         return $self->{_authortree};
  96.     }
  97.  
  98. }
  99.  
  100. =pod
  101.  
  102. =head2 $cb->_check_trees( [update_source => BOOL, path => PATH, verbose => BOOL] )
  103.  
  104. Retrieve source files and return a boolean indicating whether or not
  105. the source files are up to date.
  106.  
  107. Takes several arguments:
  108.  
  109. =over 4
  110.  
  111. =item update_source
  112.  
  113. A flag to force re-fetching of the source files, even
  114. if they are still up to date.
  115.  
  116. =item path
  117.  
  118. The absolute path to the directory holding the source files.
  119.  
  120. =item verbose
  121.  
  122. A boolean flag indicating whether or not to be verbose.
  123.  
  124. =back
  125.  
  126. Will get information from the config file by default.
  127.  
  128. =cut
  129.  
  130. ### retrieve source files, and returns a boolean indicating if it's up to date
  131. sub _check_trees {
  132.     my ($self, %hash) = @_;
  133.     my $conf          = $self->configure_object;
  134.  
  135.     my $update_source;
  136.     my $verbose;
  137.     my $path;
  138.  
  139.     my $tmpl = {
  140.         path            => { default => $conf->get_conf('base'),
  141.                              store => \$path
  142.                         },
  143.         verbose         => { default => $conf->get_conf('verbose'),
  144.                              store => \$verbose
  145.                         },
  146.         update_source   => { default => 0, store => \$update_source },
  147.     };
  148.  
  149.     my $args = check( $tmpl, \%hash ) or return;
  150.  
  151.     ### if the user never wants to update their source without explicitly
  152.     ### telling us, shortcircuit here
  153.     return 1 if $conf->get_conf('no_update') && !$update_source;
  154.  
  155.     ### a check to see if our source files are still up to date ###
  156.     msg( loc("Checking if source files are up to date"), $verbose );
  157.  
  158.     my $uptodate = 1; # default return value
  159.  
  160.     for my $name (qw[auth dslip mod]) {
  161.         for my $file ( $conf->_get_source( $name ) ) {
  162.             $self->__check_uptodate(
  163.                 file            => File::Spec->catfile( $args->{path}, $file ),
  164.                 name            => $name,
  165.                 update_source   => $update_source,
  166.                 verbose         => $verbose,
  167.             ) or $uptodate = 0;
  168.         }
  169.     }
  170.  
  171.     ### if we're explicitly asked to update the sources, or if the
  172.     ### standard source files are out of date, update the custom sources
  173.     ### as well
  174.     $self->__update_custom_module_sources( verbose => $verbose ) 
  175.         if $update_source or !$uptodate;
  176.  
  177.     return $uptodate;
  178. }
  179.  
  180. =pod
  181.  
  182. =head2 $cb->__check_uptodate( file => $file, name => $name, [update_source => BOOL, verbose => BOOL] )
  183.  
  184. C<__check_uptodate> checks if a given source file is still up-to-date
  185. and if not, or when C<update_source> is true, will re-fetch the source
  186. file.
  187.  
  188. Takes the following arguments:
  189.  
  190. =over 4
  191.  
  192. =item file
  193.  
  194. The source file to check.
  195.  
  196. =item name
  197.  
  198. The internal shortcut name for the source file (used for config
  199. lookups).
  200.  
  201. =item update_source
  202.  
  203. Flag to force updating of sourcefiles regardless.
  204.  
  205. =item verbose
  206.  
  207. Boolean to indicate whether to be verbose or not.
  208.  
  209. =back
  210.  
  211. Returns a boolean value indicating whether the current files are up
  212. to date or not.
  213.  
  214. =cut
  215.  
  216. ### this method checks whether or not the source files we are using are still up to date
  217. sub __check_uptodate {
  218.     my $self = shift;
  219.     my %hash = @_;
  220.     my $conf = $self->configure_object;
  221.  
  222.  
  223.     my $tmpl = {
  224.         file            => { required => 1 },
  225.         name            => { required => 1 },
  226.         update_source   => { default => 0 },
  227.         verbose         => { default => $conf->get_conf('verbose') },
  228.     };
  229.  
  230.     my $args = check( $tmpl, \%hash ) or return;
  231.  
  232.     my $flag;
  233.     unless ( -e $args->{'file'} && (
  234.             ( stat $args->{'file'} )[9]
  235.             + $conf->_get_source('update') )
  236.             > time ) {
  237.         $flag = 1;
  238.     }
  239.  
  240.     if ( $flag or $args->{'update_source'} ) {
  241.  
  242.          if ( $self->_update_source( name => $args->{'name'} ) ) {
  243.               return 0;       # return 0 so 'uptodate' will be set to 0, meaning no 
  244.                               # use of previously stored hashrefs!
  245.          } else {
  246.               msg( loc("Unable to update source, attempting to get away with using old source file!"), $args->{verbose} );
  247.               return 1;
  248.          }
  249.  
  250.     } else {
  251.         return 1;
  252.     }
  253. }
  254.  
  255. =pod
  256.  
  257. =head2 $cb->_update_source( name => $name, [path => $path, verbose => BOOL] )
  258.  
  259. This method does the actual fetching of source files.
  260.  
  261. It takes the following arguments:
  262.  
  263. =over 4
  264.  
  265. =item name
  266.  
  267. The internal shortcut name for the source file (used for config
  268. lookups).
  269.  
  270. =item path
  271.  
  272. The full path where to write the files.
  273.  
  274. =item verbose
  275.  
  276. Boolean to indicate whether to be verbose or not.
  277.  
  278. =back
  279.  
  280. Returns a boolean to indicate success.
  281.  
  282. =cut
  283.  
  284. ### this sub fetches new source files ###
  285. sub _update_source {
  286.     my $self = shift;
  287.     my %hash = @_;
  288.     my $conf = $self->configure_object;
  289.  
  290.     my $verbose;
  291.     my $tmpl = {
  292.         name    => { required => 1 },
  293.         path    => { default => $conf->get_conf('base') },
  294.         verbose => { default => $conf->get_conf('verbose'), store => \$verbose },
  295.     };
  296.  
  297.     my $args = check( $tmpl, \%hash ) or return;
  298.  
  299.  
  300.     my $path = $args->{path};
  301.     {   ### this could use a clean up - Kane
  302.         ### no worries about the / -> we get it from the _ftp configuration, so
  303.         ### it's not platform dependant. -kane
  304.         my ($dir, $file) = $conf->_get_mirror( $args->{'name'} ) =~ m|(.+/)(.+)$|sg;
  305.  
  306.         msg( loc("Updating source file '%1'", $file), $verbose );
  307.  
  308.         my $fake = CPANPLUS::Module::Fake->new(
  309.                         module  => $args->{'name'},
  310.                         path    => $dir,
  311.                         package => $file,
  312.                         _id     => $self->_id,
  313.                     );
  314.  
  315.         ### can't use $fake->fetch here, since ->parent won't work --
  316.         ### the sources haven't been saved yet
  317.         my $rv = $self->_fetch(
  318.                     module      => $fake,
  319.                     fetchdir    => $path,
  320.                     force       => 1,
  321.                 );
  322.  
  323.  
  324.         unless ($rv) {
  325.             error( loc("Couldn't fetch '%1'", $file) );
  326.             return;
  327.         }
  328.  
  329.         $self->_update_timestamp( file => File::Spec->catfile($path, $file) );
  330.     }
  331.  
  332.     return 1;
  333. }
  334.  
  335. =pod
  336.  
  337. =head2 $cb->_build_trees( uptodate => BOOL, [use_stored => BOOL, path => $path, verbose => BOOL] )
  338.  
  339. This method rebuilds the author- and module-trees from source.
  340.  
  341. It takes the following arguments:
  342.  
  343. =over 4
  344.  
  345. =item uptodate
  346.  
  347. Indicates whether any on disk caches are still ok to use.
  348.  
  349. =item path
  350.  
  351. The absolute path to the directory holding the source files.
  352.  
  353. =item verbose
  354.  
  355. A boolean flag indicating whether or not to be verbose.
  356.  
  357. =item use_stored
  358.  
  359. A boolean flag indicating whether or not it is ok to use previously
  360. stored trees. Defaults to true.
  361.  
  362. =back
  363.  
  364. Returns a boolean indicating success.
  365.  
  366. =cut
  367.  
  368. ### (re)build the trees ###
  369. sub _build_trees {
  370.     my ($self, %hash)   = @_;
  371.     my $conf            = $self->configure_object;
  372.  
  373.     my($path,$uptodate,$use_stored);
  374.     my $tmpl = {
  375.         path        => { default => $conf->get_conf('base'), store => \$path },
  376.         verbose     => { default => $conf->get_conf('verbose') },
  377.         uptodate    => { required => 1, store => \$uptodate },
  378.         use_stored  => { default => 1, store => \$use_stored },
  379.     };
  380.  
  381.     my $args = check( $tmpl, \%hash ) or return undef;
  382.  
  383.     ### retrieve the stored source files ###
  384.     my $stored      = $self->__retrieve_source(
  385.                             path        => $path,
  386.                             uptodate    => $uptodate && $use_stored,
  387.                             verbose     => $args->{'verbose'},
  388.                         ) || {};
  389.  
  390.     ### build the trees ###
  391.     $self->{_authortree} =  $stored->{_authortree} ||
  392.                             $self->__create_author_tree(
  393.                                     uptodate    => $uptodate,
  394.                                     path        => $path,
  395.                                     verbose     => $args->{verbose},
  396.                                 );
  397.     $self->{_modtree}    =  $stored->{_modtree} ||
  398.                             $self->_create_mod_tree(
  399.                                     uptodate    => $uptodate,
  400.                                     path        => $path,
  401.                                     verbose     => $args->{verbose},
  402.                                 );
  403.  
  404.     ### return if we weren't able to build the trees ###
  405.     return unless $self->{_modtree} && $self->{_authortree};
  406.  
  407.     ### update them if the other sources are also deemed out of date
  408.     unless( $uptodate ) {
  409.         $self->__update_custom_module_sources( verbose => $args->{verbose} ) 
  410.             or error(loc("Could not update custom module sources"));
  411.     }      
  412.  
  413.     ### add custom sources here
  414.     $self->__create_custom_module_entries( verbose => $args->{verbose} )
  415.         or error(loc("Could not create custom module entries"));
  416.  
  417.     ### write the stored files to disk, so we can keep using them
  418.     ### from now on, till they become invalid
  419.     ### write them if the original sources weren't uptodate, or
  420.     ### we didn't just load storable files
  421.     $self->_save_source() if !$uptodate or not keys %$stored;
  422.  
  423.     ### still necessary? can only run one instance now ###
  424.     ### will probably stay that way --kane
  425. #     my $id = $self->_store_id( $self );
  426. #
  427. #     unless ( $id == $self->_id ) {
  428. #         error( loc("IDs do not match: %1 != %2. Storage failed!", $id, $self->_id) );
  429. #     }
  430.  
  431.     return 1;
  432. }
  433.  
  434. =pod
  435.  
  436. =head2 $cb->__retrieve_source(name => $name, [path => $path, uptodate => BOOL, verbose => BOOL])
  437.  
  438. This method retrieves a I<storable>d tree identified by C<$name>.
  439.  
  440. It takes the following arguments:
  441.  
  442. =over 4
  443.  
  444. =item name
  445.  
  446. The internal name for the source file to retrieve.
  447.  
  448. =item uptodate
  449.  
  450. A flag indicating whether the file-cache is up-to-date or not.
  451.  
  452. =item path
  453.  
  454. The absolute path to the directory holding the source files.
  455.  
  456. =item verbose
  457.  
  458. A boolean flag indicating whether or not to be verbose.
  459.  
  460. =back
  461.  
  462. Will get information from the config file by default.
  463.  
  464. Returns a tree on success, false on failure.
  465.  
  466. =cut
  467.  
  468. sub __retrieve_source {
  469.     my $self = shift;
  470.     my %hash = @_;
  471.     my $conf = $self->configure_object;
  472.  
  473.     my $tmpl = {
  474.         path     => { default => $conf->get_conf('base') },
  475.         verbose  => { default => $conf->get_conf('verbose') },
  476.         uptodate => { default => 0 },
  477.     };
  478.  
  479.     my $args = check( $tmpl, \%hash ) or return;
  480.  
  481.     ### check if we can retrieve a frozen data structure with storable ###
  482.     my $storable = can_load( modules => {'Storable' => '0.0'} )
  483.                         if $conf->get_conf('storable');
  484.  
  485.     return unless $storable;
  486.  
  487.     ### $stored is the name of the frozen data structure ###
  488.     my $stored = $self->__storable_file( $args->{path} );
  489.  
  490.     if ($storable && -e $stored && -s _ && $args->{'uptodate'}) {
  491.         msg( loc("Retrieving %1", $stored), $args->{'verbose'} );
  492.  
  493.         my $href = Storable::retrieve($stored);
  494.         return $href;
  495.     } else {
  496.         return;
  497.     }
  498. }
  499.  
  500. =pod
  501.  
  502. =head2 $cb->_save_source([verbose => BOOL, path => $path])
  503.  
  504. This method saves all the parsed trees in I<storable>d format if
  505. C<Storable> is available.
  506.  
  507. It takes the following arguments:
  508.  
  509. =over 4
  510.  
  511. =item path
  512.  
  513. The absolute path to the directory holding the source files.
  514.  
  515. =item verbose
  516.  
  517. A boolean flag indicating whether or not to be verbose.
  518.  
  519. =back
  520.  
  521. Will get information from the config file by default.
  522.  
  523. Returns true on success, false on failure.
  524.  
  525. =cut
  526.  
  527. sub _save_source {
  528.     my $self = shift;
  529.     my %hash = @_;
  530.     my $conf = $self->configure_object;
  531.  
  532.  
  533.     my $tmpl = {
  534.         path     => { default => $conf->get_conf('base'), allow => DIR_EXISTS },
  535.         verbose  => { default => $conf->get_conf('verbose') },
  536.         force    => { default => 1 },
  537.     };
  538.  
  539.     my $args = check( $tmpl, \%hash ) or return;
  540.  
  541.     my $aref = [qw[_modtree _authortree]];
  542.  
  543.     ### check if we can retrieve a frozen data structure with storable ###
  544.     my $storable;
  545.     $storable = can_load( modules => {'Storable' => '0.0'} )
  546.                     if $conf->get_conf('storable');
  547.     return unless $storable;
  548.  
  549.     my $to_write = {};
  550.     foreach my $key ( @$aref ) {
  551.         next unless ref( $self->{$key} );
  552.         $to_write->{$key} = $self->{$key};
  553.     }
  554.  
  555.     return unless keys %$to_write;
  556.  
  557.     ### $stored is the name of the frozen data structure ###
  558.     my $stored = $self->__storable_file( $args->{path} );
  559.  
  560.     if (-e $stored && not -w $stored) {
  561.         msg( loc("%1 not writable; skipped.", $stored), $args->{'verbose'} );
  562.         return;
  563.     }
  564.  
  565.     msg( loc("Writing compiled source information to disk. This might take a little while."),
  566.         $args->{'verbose'} );
  567.  
  568.     my $flag;
  569.     unless( Storable::nstore( $to_write, $stored ) ) {
  570.         error( loc("could not store %1!", $stored) );
  571.         $flag++;
  572.     }
  573.  
  574.     return $flag ? 0 : 1;
  575. }
  576.  
  577. sub __storable_file {
  578.     my $self = shift;
  579.     my $conf = $self->configure_object;
  580.     my $path = shift or return;
  581.  
  582.     ### check if we can retrieve a frozen data structure with storable ###
  583.     my $storable = $conf->get_conf('storable')
  584.                         ? can_load( modules => {'Storable' => '0.0'} )
  585.                         : 0;
  586.  
  587.     return unless $storable;
  588.     
  589.     ### $stored is the name of the frozen data structure ###
  590.     ### changed to use File::Spec->catfile -jmb
  591.     my $stored = File::Spec->rel2abs(
  592.         File::Spec->catfile(
  593.             $path,                          #base dir
  594.             $conf->_get_source('stored')    #file
  595.             . '.' .
  596.             $Storable::VERSION              #the version of storable 
  597.             . '.stored'                     #append a suffix
  598.         )
  599.     );
  600.  
  601.     return $stored;
  602. }
  603.  
  604. =pod
  605.  
  606. =head2 $cb->__create_author_tree([path => $path, uptodate => BOOL, verbose => BOOL])
  607.  
  608. This method opens a source files and parses its contents into a
  609. searchable author-tree or restores a file-cached version of a
  610. previous parse, if the sources are uptodate and the file-cache exists.
  611.  
  612. It takes the following arguments:
  613.  
  614. =over 4
  615.  
  616. =item uptodate
  617.  
  618. A flag indicating whether the file-cache is uptodate or not.
  619.  
  620. =item path
  621.  
  622. The absolute path to the directory holding the source files.
  623.  
  624. =item verbose
  625.  
  626. A boolean flag indicating whether or not to be verbose.
  627.  
  628. =back
  629.  
  630. Will get information from the config file by default.
  631.  
  632. Returns a tree on success, false on failure.
  633.  
  634. =cut
  635.  
  636. sub __create_author_tree {
  637.     my $self = shift;
  638.     my %hash = @_;
  639.     my $conf = $self->configure_object;
  640.  
  641.  
  642.     my $tmpl = {
  643.         path     => { default => $conf->get_conf('base') },
  644.         verbose  => { default => $conf->get_conf('verbose') },
  645.         uptodate => { default => 0 },
  646.     };
  647.  
  648.     my $args = check( $tmpl, \%hash ) or return;
  649.     my $tree = {};
  650.     my $file = File::Spec->catfile(
  651.                                 $args->{path},
  652.                                 $conf->_get_source('auth')
  653.                             );
  654.  
  655.     msg(loc("Rebuilding author tree, this might take a while"),
  656.         $args->{verbose});
  657.  
  658.     ### extract the file ###
  659.     my $ae      = Archive::Extract->new( archive => $file ) or return;
  660.     my $out     = STRIP_GZ_SUFFIX->($file);
  661.  
  662.     ### make sure to set the PREFER_BIN flag if desired ###
  663.     {   local $Archive::Extract::PREFER_BIN = $conf->get_conf('prefer_bin');
  664.         $ae->extract( to => $out )                              or return;
  665.     }
  666.  
  667.     my $cont    = $self->_get_file_contents( file => $out ) or return;
  668.  
  669.     ### don't need it anymore ###
  670.     unlink $out;
  671.  
  672.     for ( split /\n/, $cont ) {
  673.         my($id, $name, $email) = m/^alias \s+
  674.                                     (\S+) \s+
  675.                                     "\s* ([^\"\<]+?) \s* <(.+)> \s*"
  676.                                 /x;
  677.  
  678.         $tree->{$id} = CPANPLUS::Module::Author->new(
  679.             author  => $name,           #authors name
  680.             email   => $email,          #authors email address
  681.             cpanid  => $id,             #authors CPAN ID
  682.             _id     => $self->_id,    #id of this internals object
  683.         );
  684.     }
  685.  
  686.     return $tree;
  687.  
  688. } #__create_author_tree
  689.  
  690. =pod
  691.  
  692. =head2 $cb->_create_mod_tree([path => $path, uptodate => BOOL, verbose => BOOL])
  693.  
  694. This method opens a source files and parses its contents into a
  695. searchable module-tree or restores a file-cached version of a
  696. previous parse, if the sources are uptodate and the file-cache exists.
  697.  
  698. It takes the following arguments:
  699.  
  700. =over 4
  701.  
  702. =item uptodate
  703.  
  704. A flag indicating whether the file-cache is up-to-date or not.
  705.  
  706. =item path
  707.  
  708. The absolute path to the directory holding the source files.
  709.  
  710. =item verbose
  711.  
  712. A boolean flag indicating whether or not to be verbose.
  713.  
  714. =back
  715.  
  716. Will get information from the config file by default.
  717.  
  718. Returns a tree on success, false on failure.
  719.  
  720. =cut
  721.  
  722. ### this builds a hash reference with the structure of the cpan module tree ###
  723. sub _create_mod_tree {
  724.     my $self = shift;
  725.     my %hash = @_;
  726.     my $conf = $self->configure_object;
  727.  
  728.  
  729.     my $tmpl = {
  730.         path     => { default => $conf->get_conf('base') },
  731.         verbose  => { default => $conf->get_conf('verbose') },
  732.         uptodate => { default => 0 },
  733.     };
  734.  
  735.     my $args = check( $tmpl, \%hash ) or return undef;
  736.     my $file = File::Spec->catfile($args->{path}, $conf->_get_source('mod'));
  737.  
  738.     msg(loc("Rebuilding module tree, this might take a while"),
  739.         $args->{verbose});
  740.  
  741.  
  742.     my $dslip_tree = $self->__create_dslip_tree( %$args );
  743.  
  744.     ### extract the file ###
  745.     my $ae      = Archive::Extract->new( archive => $file ) or return;
  746.     my $out     = STRIP_GZ_SUFFIX->($file);
  747.  
  748.     ### make sure to set the PREFER_BIN flag if desired ###
  749.     {   local $Archive::Extract::PREFER_BIN = $conf->get_conf('prefer_bin');
  750.         $ae->extract( to => $out )                              or return;
  751.     }
  752.  
  753.     my $cont    = $self->_get_file_contents( file => $out ) or return;
  754.  
  755.     ### don't need it anymore ###
  756.     unlink $out;
  757.  
  758.     my $tree = {};
  759.     my $flag;
  760.  
  761.     for ( split /\n/, $cont ) {
  762.  
  763.         ### quick hack to read past the header of the file ###
  764.         ### this is still rather evil... fix some time - Kane
  765.         $flag = 1 if m|^\s*$|;
  766.         next unless $flag;
  767.  
  768.         ### skip empty lines ###
  769.         next unless /\S/;
  770.         chomp;
  771.  
  772.         my @data = split /\s+/;
  773.  
  774.         ### filter out the author and filename as well ###
  775.         ### authors can apparently have digits in their names,
  776.         ### and dirs can have dots... blah!
  777.         my ($author, $package) = $data[2] =~
  778.                 m|  (?:[A-Z\d-]/)?
  779.                     (?:[A-Z\d-]{2}/)?
  780.                     ([A-Z\d-]+) (?:/[\S]+)?/
  781.                     ([^/]+)$
  782.                 |xsg;
  783.  
  784.         ### remove file name from the path
  785.         $data[2] =~ s|/[^/]+$||;
  786.  
  787.  
  788.         unless( $self->author_tree($author) ) {
  789.             error( loc( "No such author '%1' -- can't make module object " .
  790.                         "'%2' that is supposed to belong to this author",
  791.                         $author, $data[0] ) );
  792.             next;
  793.         }
  794.  
  795.         ### adding the dslip info
  796.         ### probably can use some optimization
  797.         my $dslip;
  798.         for my $item ( qw[ statd stats statl stati statp ] ) {
  799.             ### checking if there's an entry in the dslip info before
  800.             ### catting it on. appeasing warnings this way
  801.             $dslip .=   $dslip_tree->{ $data[0] }->{$item}
  802.                             ? $dslip_tree->{ $data[0] }->{$item}
  803.                             : ' ';
  804.         }
  805.  
  806.         ### Every module get's stored as a module object ###
  807.         $tree->{ $data[0] } = CPANPLUS::Module->new(
  808.                 module      => $data[0],            # full module name
  809.                 version     => ($data[1] eq 'undef' # version number 
  810.                                     ? '0.0' 
  811.                                     : $data[1]), 
  812.                 path        => File::Spec::Unix->catfile(
  813.                                     $conf->_get_mirror('base'),
  814.                                     $data[2],
  815.                                 ),          # extended path on the cpan mirror,
  816.                                             # like /A/AB/ABIGAIL
  817.                 comment     => $data[3],    # comment on the module
  818.                 author      => $self->author_tree($author),
  819.                 package     => $package,    # package name, like
  820.                                             # 'foo-bar-baz-1.03.tar.gz'
  821.                 description => $dslip_tree->{ $data[0] }->{'description'},
  822.                 dslip       => $dslip,
  823.                 _id         => $self->_id,  # id of this internals object
  824.         );
  825.  
  826.     } #for
  827.  
  828.     return $tree;
  829.  
  830. } #_create_mod_tree
  831.  
  832. =pod
  833.  
  834. =head2 $cb->__create_dslip_tree([path => $path, uptodate => BOOL, verbose => BOOL])
  835.  
  836. This method opens a source files and parses its contents into a
  837. searchable dslip-tree or restores a file-cached version of a
  838. previous parse, if the sources are uptodate and the file-cache exists.
  839.  
  840. It takes the following arguments:
  841.  
  842. =over 4
  843.  
  844. =item uptodate
  845.  
  846. A flag indicating whether the file-cache is uptodate or not.
  847.  
  848. =item path
  849.  
  850. The absolute path to the directory holding the source files.
  851.  
  852. =item verbose
  853.  
  854. A boolean flag indicating whether or not to be verbose.
  855.  
  856. =back
  857.  
  858. Will get information from the config file by default.
  859.  
  860. Returns a tree on success, false on failure.
  861.  
  862. =cut
  863.  
  864. sub __create_dslip_tree {
  865.     my $self = shift;
  866.     my %hash = @_;
  867.     my $conf = $self->configure_object;
  868.  
  869.     my $tmpl = {
  870.         path     => { default => $conf->get_conf('base') },
  871.         verbose  => { default => $conf->get_conf('verbose') },
  872.         uptodate => { default => 0 },
  873.     };
  874.  
  875.     my $args = check( $tmpl, \%hash ) or return;
  876.  
  877.     ### get the file name of the source ###
  878.     my $file = File::Spec->catfile($args->{path}, $conf->_get_source('dslip'));
  879.  
  880.     ### extract the file ###
  881.     my $ae      = Archive::Extract->new( archive => $file ) or return;
  882.     my $out     = STRIP_GZ_SUFFIX->($file);
  883.  
  884.     ### make sure to set the PREFER_BIN flag if desired ###
  885.     {   local $Archive::Extract::PREFER_BIN = $conf->get_conf('prefer_bin');
  886.         $ae->extract( to => $out )                              or return;
  887.     }
  888.  
  889.     my $in      = $self->_get_file_contents( file => $out ) or return;
  890.  
  891.     ### don't need it anymore ###
  892.     unlink $out;
  893.  
  894.  
  895.     ### get rid of the comments and the code ###
  896.     ### need a smarter parser, some people have this in their dslip info:
  897.     # [
  898.     # 'Statistics::LTU',
  899.     # 'R',
  900.     # 'd',
  901.     # 'p',
  902.     # 'O',
  903.     # '?',
  904.     # 'Implements Linear Threshold Units',
  905.     # ...skipping...
  906.     # "\x{c4}dd \x{fc}ml\x{e4}\x{fc}ts t\x{f6} \x{eb}v\x{eb}r\x{ff}th\x{ef}ng!",
  907.     # 'BENNIE',
  908.     # '11'
  909.     # ],
  910.     ### also, older versions say:
  911.     ### $cols = [....]
  912.     ### and newer versions say:
  913.     ### $CPANPLUS::Modulelist::cols = [...]
  914.     ### split '$cols' and '$data' into 2 variables ###
  915.     ### use this regex to make sure dslips with ';' in them don't cause
  916.     ### parser errors
  917.     my ($ds_one, $ds_two) = ($in =~ m|.+}\s+
  918.                                         (\$(?:CPAN::Modulelist::)?cols.*?)
  919.                                         (\$(?:CPAN::Modulelist::)?data.*)
  920.                                     |sx);
  921.  
  922.     ### eval them into existence ###
  923.     ### still not too fond of this solution - kane ###
  924.     my ($cols, $data);
  925.     {   #local $@; can't use this, it's buggy -kane
  926.  
  927.         $cols = eval $ds_one;
  928.         error( loc("Error in eval of dslip source files: %1", $@) ) if $@;
  929.  
  930.         $data = eval $ds_two;
  931.         error( loc("Error in eval of dslip source files: %1", $@) ) if $@;
  932.  
  933.     }
  934.  
  935.     my $tree = {};
  936.     my $primary = "modid";
  937.  
  938.     ### this comes from CPAN::Modulelist
  939.     ### which is in 03modlist.data.gz
  940.     for (@$data){
  941.         my %hash;
  942.         @hash{@$cols} = @$_;
  943.         $tree->{$hash{$primary}} = \%hash;
  944.     }
  945.  
  946.     return $tree;
  947.  
  948. } #__create_dslip_tree
  949.  
  950. =pod
  951.  
  952. =head2 $cb->_dslip_defs ()
  953.  
  954. This function returns the definition structure (ARRAYREF) of the
  955. dslip tree.
  956.  
  957. =cut
  958.  
  959. ### these are the definitions used for dslip info
  960. ### they shouldn't change over time.. so hardcoding them doesn't appear to
  961. ### be a problem. if it is, we need to parse 03modlist.data better to filter
  962. ### all this out.
  963. ### right now, this is just used to look up dslip info from a module
  964. sub _dslip_defs {
  965.     my $self = shift;
  966.  
  967.     my $aref = [
  968.  
  969.         # D
  970.         [ q|Development Stage|, {
  971.             i   => loc('Idea, listed to gain consensus or as a placeholder'),
  972.             c   => loc('under construction but pre-alpha (not yet released)'),
  973.             a   => loc('Alpha testing'),
  974.             b   => loc('Beta testing'),
  975.             R   => loc('Released'),
  976.             M   => loc('Mature (no rigorous definition)'),
  977.             S   => loc('Standard, supplied with Perl 5'),
  978.         }],
  979.  
  980.         # S
  981.         [ q|Support Level|, {
  982.             m   => loc('Mailing-list'),
  983.             d   => loc('Developer'),
  984.             u   => loc('Usenet newsgroup comp.lang.perl.modules'),
  985.             n   => loc('None known, try comp.lang.perl.modules'),
  986.             a   => loc('Abandoned; volunteers welcome to take over maintainance'),
  987.         }],
  988.  
  989.         # L
  990.         [ q|Language Used|, {
  991.             p   => loc('Perl-only, no compiler needed, should be platform independent'),
  992.             c   => loc('C and perl, a C compiler will be needed'),
  993.             h   => loc('Hybrid, written in perl with optional C code, no compiler needed'),
  994.             '+' => loc('C++ and perl, a C++ compiler will be needed'),
  995.             o   => loc('perl and another language other than C or C++'),
  996.         }],
  997.  
  998.         # I
  999.         [ q|Interface Style|, {
  1000.             f   => loc('plain Functions, no references used'),
  1001.             h   => loc('hybrid, object and function interfaces available'),
  1002.             n   => loc('no interface at all (huh?)'),
  1003.             r   => loc('some use of unblessed References or ties'),
  1004.             O   => loc('Object oriented using blessed references and/or inheritance'),
  1005.         }],
  1006.  
  1007.         # P
  1008.         [ q|Public License|, {
  1009.             p   => loc('Standard-Perl: user may choose between GPL and Artistic'),
  1010.             g   => loc('GPL: GNU General Public License'),
  1011.             l   => loc('LGPL: "GNU Lesser General Public License" (previously known as "GNU Library General Public License")'),
  1012.             b   => loc('BSD: The BSD License'),
  1013.             a   => loc('Artistic license alone'),
  1014.             o   => loc('other (but distribution allowed without restrictions)'),
  1015.         }],
  1016.     ];
  1017.  
  1018.     return $aref;
  1019. }
  1020.  
  1021. =head2 $file = $cb->_add_custom_module_source( uri => URI, [verbose => BOOL] ); 
  1022.  
  1023. Adds a custom source index and updates it based on the provided URI.
  1024.  
  1025. Returns the full path to the index file on success or false on failure.
  1026.  
  1027. =cut
  1028.  
  1029. sub _add_custom_module_source {
  1030.     my $self = shift;
  1031.     my $conf = $self->configure_object;
  1032.     my %hash = @_;
  1033.     
  1034.     my($verbose,$uri);
  1035.     my $tmpl = {   
  1036.         verbose => { default => $conf->get_conf('verbose'),
  1037.                      store   => \$verbose },
  1038.         uri     => { required => 1, store => \$uri }
  1039.     };
  1040.     
  1041.     check( $tmpl, \%hash ) or return;
  1042.     
  1043.     ### what index file should we use on disk?
  1044.     my $index = $self->__custom_module_source_index_file( uri => $uri );
  1045.  
  1046.     ### already have it.
  1047.     if( IS_FILE->( $index ) ) {
  1048.         msg(loc("Source '%1' already added", $uri));
  1049.         return 1;
  1050.     }        
  1051.         
  1052.     ### do we need to create the targe dir?        
  1053.     {   my $dir = dirname( $index );
  1054.         unless( IS_DIR->( $dir ) ) {
  1055.             $self->_mkdir( dir => $dir ) or return
  1056.         }
  1057.     }  
  1058.     
  1059.     ### write the file
  1060.     my $fh = OPEN_FILE->( $index => '>' ) or do {
  1061.         error(loc("Could not open index file for '%1'", $uri));
  1062.         return;
  1063.     };
  1064.     
  1065.     ### basically we 'touched' it. Check the return value, may be 
  1066.     ### important on win32 and similar OS, where there's file length
  1067.     ### limits
  1068.     close $fh or do {
  1069.         error(loc("Could not write index file to disk for '%1'", $uri));
  1070.         return;
  1071.     };        
  1072.         
  1073.     $self->__update_custom_module_source(
  1074.                 remote  => $uri,
  1075.                 local   => $index,
  1076.                 verbose => $verbose,
  1077.             ) or do {
  1078.                 ### we faild to update it, we probably have an empty
  1079.                 ### possibly silly filename on disk now -- remove it
  1080.                 1 while unlink $index;
  1081.                 return;                
  1082.             };
  1083.             
  1084.     return $index;
  1085. }
  1086.  
  1087. =head2 $index = $cb->__custom_module_source_index_file( uri => $uri );
  1088.  
  1089. Returns the full path to the encoded index file for C<$uri>, as used by
  1090. all C<custom module source> routines.
  1091.  
  1092. =cut
  1093.  
  1094. sub __custom_module_source_index_file {
  1095.     my $self = shift;
  1096.     my $conf = $self->configure_object;
  1097.     my %hash = @_;
  1098.     
  1099.     my($verbose,$uri);
  1100.     my $tmpl = {   
  1101.         uri     => { required => 1, store => \$uri }
  1102.     };
  1103.     
  1104.     check( $tmpl, \%hash ) or return;
  1105.     
  1106.     my $index = File::Spec->catfile(
  1107.                     $conf->get_conf('base'),
  1108.                     $conf->_get_build('custom_sources'),        
  1109.                     $self->_uri_encode( uri => $uri ),
  1110.                 );     
  1111.  
  1112.     return $index;
  1113. }
  1114.  
  1115. =head2 $file = $cb->_remove_custom_module_source( uri => URI, [verbose => BOOL] ); 
  1116.  
  1117. Removes a custom index file based on the URI provided.
  1118.  
  1119. Returns the full path to the index file on success or false on failure.
  1120.  
  1121. =cut
  1122.  
  1123. sub _remove_custom_module_source {
  1124.     my $self = shift;
  1125.     my $conf = $self->configure_object;
  1126.     my %hash = @_;
  1127.     
  1128.     my($verbose,$uri);
  1129.     my $tmpl = {   
  1130.         verbose => { default => $conf->get_conf('verbose'),
  1131.                      store   => \$verbose },
  1132.         uri     => { required => 1, store => \$uri }
  1133.     };
  1134.     
  1135.     check( $tmpl, \%hash ) or return;
  1136.  
  1137.     ### use uri => local, instead of the other way around
  1138.     my %files = reverse $self->__list_custom_module_sources;
  1139.     
  1140.     ### On VMS the case of key to %files can be either exact or lower case
  1141.     ### XXX abstract this lookup out? --kane
  1142.     my $file = $files{ $uri };
  1143.     $file    = $files{ lc $uri } if !defined($file) && ON_VMS;
  1144.  
  1145.     unless (defined $file) {
  1146.         error(loc("No such custom source '%1'", $uri));
  1147.         return;
  1148.     };
  1149.                 
  1150.     1 while unlink $file;
  1151.  
  1152.     if( IS_FILE->( $file ) ) {
  1153.         error(loc("Could not remove index file '%1' for custom source '%2'",
  1154.                     $file, $uri));
  1155.         return;
  1156.     }    
  1157.             
  1158.     msg(loc("Successfully removed index file for '%1'", $uri), $verbose);
  1159.  
  1160.     return $file;
  1161. }
  1162.  
  1163. =head2 %files = $cb->__list_custom_module_sources
  1164.  
  1165. This method scans the 'custom-sources' directory in your base directory
  1166. for additional sources to include in your module tree.
  1167.  
  1168. Returns a list of key value pairs as follows:
  1169.  
  1170.   /full/path/to/source/file%3Fencoded => http://decoded/mirror/path
  1171.  
  1172. =cut
  1173.  
  1174. sub __list_custom_module_sources {
  1175.     my $self = shift;
  1176.     my $conf = $self->configure_object;
  1177.  
  1178.     my $dir = File::Spec->catdir(
  1179.                     $conf->get_conf('base'),
  1180.                     $conf->_get_build('custom_sources'),
  1181.                 );
  1182.  
  1183.     unless( IS_DIR->( $dir ) ) {
  1184.         msg(loc("No '%1' dir, skipping custom sources", $dir));
  1185.         return;
  1186.     }
  1187.     
  1188.     ### unencode the files
  1189.     ### skip ones starting with # though
  1190.     my %files = map {            
  1191.         my $org = $_;            
  1192.         my $dec = $self->_uri_decode( uri => $_ );            
  1193.         File::Spec->catfile( $dir, $org ) => $dec
  1194.     } grep { $_ !~ /^#/ } READ_DIR->( $dir );        
  1195.  
  1196.     return %files;    
  1197. }
  1198.  
  1199. =head2 $bool = $cb->__update_custom_module_sources( [verbose => BOOL] );
  1200.  
  1201. Attempts to update all the index files to your custom module sources.
  1202.  
  1203. If the index is missing, and it's a C<file://> uri, it will generate
  1204. a new local index for you.
  1205.  
  1206. Return true on success, false on failure.
  1207.  
  1208. =cut
  1209.  
  1210. sub __update_custom_module_sources {
  1211.     my $self = shift;
  1212.     my $conf = $self->configure_object;
  1213.     my %hash = @_;
  1214.     
  1215.     my $verbose;
  1216.     my $tmpl = {   
  1217.         verbose => { default => $conf->get_conf('verbose'),
  1218.                      store   => \$verbose }
  1219.     };
  1220.     
  1221.     check( $tmpl, \%hash ) or return;
  1222.     
  1223.     my %files = $self->__list_custom_module_sources;
  1224.     
  1225.     ### uptodate check has been done a few levels up.   
  1226.     my $fail;
  1227.     while( my($local,$remote) = each %files ) {
  1228.         
  1229.         $self->__update_custom_module_source(
  1230.                     remote  => $remote,
  1231.                     local   => $local,
  1232.                     verbose => $verbose,
  1233.                 ) or ( $fail++, next );         
  1234.     }
  1235.     
  1236.     error(loc("Failed updating one or more remote sources files")) if $fail;
  1237.     
  1238.     return if $fail;
  1239.     return 1;
  1240. }
  1241.  
  1242. =head2 $ok = $cb->__update_custom_module_source 
  1243.  
  1244. Attempts to update all the index files to your custom module sources.
  1245.  
  1246. If the index is missing, and it's a C<file://> uri, it will generate
  1247. a new local index for you.
  1248.  
  1249. Return true on success, false on failure.
  1250.  
  1251. =cut
  1252.  
  1253. sub __update_custom_module_source {
  1254.     my $self = shift;
  1255.     my $conf = $self->configure_object;
  1256.     my %hash = @_;
  1257.     
  1258.     my($verbose,$local,$remote);
  1259.     my $tmpl = {   
  1260.         verbose => { default  => $conf->get_conf('verbose'),
  1261.                      store    => \$verbose },
  1262.         local   => { store    => \$local, allow => FILE_EXISTS },
  1263.         remote  => { required => 1, store => \$remote },
  1264.     };
  1265.  
  1266.     check( $tmpl, \%hash ) or return;
  1267.  
  1268.     msg( loc("Updating sources from '%1'", $remote), $verbose);
  1269.     
  1270.     ### if you didn't provide a local file, we'll look in your custom
  1271.     ### dir to find the local encoded version for you
  1272.     $local ||= do {
  1273.         ### find all files we know of
  1274.         my %files = reverse $self->__list_custom_module_sources or do {
  1275.             error(loc("No custom modules sources defined -- need '%1' argument",
  1276.                       'local'));
  1277.             return;                      
  1278.         };
  1279.  
  1280.         ### On VMS the case of key to %files can be either exact or lower case
  1281.         ### XXX abstract this lookup out? --kane
  1282.         my $file = $files{ $remote };
  1283.         $file    = $files{ lc $remote } if !defined ($file) && ON_VMS;
  1284.  
  1285.         ### return the local file we're supposed to use
  1286.         $file or do {
  1287.             error(loc("Remote source '%1' unknown -- needs '%2' argument",
  1288.                       $remote, 'local'));
  1289.             return;
  1290.         };         
  1291.     };
  1292.     
  1293.     my $uri =  join '/', $remote, $conf->_get_source('custom_index');
  1294.     my $ff  =  File::Fetch->new( uri => $uri );           
  1295.  
  1296.     ### tempdir doesn't clean up by default, as opposed to tempfile()
  1297.     ### so add it explicitly.
  1298.     my $dir =  tempdir( CLEANUP => 1 );
  1299.     
  1300.     my $res =  do {  local $File::Fetch::WARN = 0;
  1301.                     local $File::Fetch::WARN = 0;
  1302.                     $ff->fetch( to => $dir );
  1303.                 };
  1304.  
  1305.     ### couldn't get the file
  1306.     unless( $res ) {
  1307.         
  1308.         ### it's not a local scheme, so can't auto index
  1309.         unless( $ff->scheme eq 'file' ) {
  1310.             error(loc("Could not update sources from '%1': %2",
  1311.                       $remote, $ff->error ));
  1312.             return;   
  1313.                         
  1314.         ### it's a local uri, we can index it ourselves
  1315.         } else {
  1316.             msg(loc("No index file found at '%1', generating one",
  1317.                     $ff->uri), $verbose );
  1318.             
  1319.             ### ON VMS, if you are working with a UNIX file specification,
  1320.             ### you need currently use the UNIX variants of the File::Spec.
  1321.             my $ff_path = do {
  1322.                 my $file_class = 'File::Spec';
  1323.                 $file_class .= '::Unix' if ON_VMS;
  1324.                 $file_class->catdir( File::Spec::Unix->splitdir( $ff->path ) );
  1325.             };      
  1326.  
  1327.             $self->__write_custom_module_index(
  1328.                 path    => $ff_path,
  1329.                 to      => $local,
  1330.                 verbose => $verbose,
  1331.             ) or return;
  1332.             
  1333.             ### XXX don't write that here, __write_custom_module_index
  1334.             ### already prints this out
  1335.             #msg(loc("Index file written to '%1'", $to), $verbose);
  1336.         }
  1337.     
  1338.     ### copy it to the real spot and update it's timestamp
  1339.     } else {            
  1340.         $self->_move( file => $res, to => $local ) or return;
  1341.         $self->_update_timestamp( file => $local );
  1342.         
  1343.         msg(loc("Index file saved to '%1'", $local), $verbose);
  1344.     }
  1345.     
  1346.     return $local;
  1347. }
  1348.  
  1349. =head2 $bool = $cb->__write_custom_module_index( path => /path/to/packages, [to => /path/to/index/file, verbose => BOOL] )
  1350.  
  1351. Scans the C<path> you provided for packages and writes an index with all 
  1352. the available packages to C<$path/packages.txt>. If you'd like the index
  1353. to be written to a different file, provide the C<to> argument.
  1354.  
  1355. Returns true on success and false on failure.
  1356.  
  1357. =cut
  1358.  
  1359. sub __write_custom_module_index {
  1360.     my $self = shift;
  1361.     my $conf = $self->configure_object;
  1362.     my %hash = @_;
  1363.     
  1364.     my ($verbose, $path, $to);
  1365.     my $tmpl = {   
  1366.         verbose => { default => $conf->get_conf('verbose'),
  1367.                      store   => \$verbose },
  1368.         path    => { required => 1, allow => DIR_EXISTS, store => \$path },
  1369.         to      => { store => \$to },
  1370.     };
  1371.     
  1372.     check( $tmpl, \%hash ) or return;    
  1373.  
  1374.     ### no explicit to? then we'll use our default
  1375.     $to ||= File::Spec->catfile( $path, $conf->_get_source('custom_index') );
  1376.  
  1377.     my @files;
  1378.     require File::Find;
  1379.     File::Find::find( sub { 
  1380.         ### let's see if A::E can even parse it
  1381.         my $ae = do {
  1382.             local $Archive::Extract::WARN = 0;
  1383.             local $Archive::Extract::WARN = 0;
  1384.             Archive::Extract->new( archive => $File::Find::name ) 
  1385.         } or return; 
  1386.  
  1387.         ### it's a type A::E recognize, so we can add it
  1388.         $ae->type or return;
  1389.  
  1390.         ### neither $_ nor $File::Find::name have the chunk of the path in
  1391.         ### it starting $path -- it's either only the filename, or the full
  1392.         ### path, so we have to strip it ourselves
  1393.         ### make sure to remove the leading slash as well.
  1394.         my $copy = $File::Find::name;
  1395.         my $re   = quotemeta($path);        
  1396.         $copy    =~ s|^$re[\\/]?||i;
  1397.         
  1398.         push @files, $copy;
  1399.         
  1400.     }, $path );
  1401.  
  1402.     ### does the dir exist? if not, create it.
  1403.     {   my $dir = dirname( $to );
  1404.         unless( IS_DIR->( $dir ) ) {
  1405.             $self->_mkdir( dir => $dir ) or return
  1406.         }
  1407.     }        
  1408.  
  1409.     ### create the index file
  1410.     my $fh = OPEN_FILE->( $to => '>' ) or return;
  1411.     
  1412.     print $fh "$_\n" for @files;
  1413.     close $fh;
  1414.     
  1415.     msg(loc("Successfully written index file to '%1'", $to), $verbose);
  1416.     
  1417.     return $to;
  1418. }
  1419.  
  1420.  
  1421. =head2 $bool = $cb->__create_custom_module_entries( [verbose => BOOL] ) 
  1422.  
  1423. Creates entries in the module tree based upon the files as returned
  1424. by C<__list_custom_module_sources>.
  1425.  
  1426. Returns true on success, false on failure.
  1427.  
  1428. =cut 
  1429.  
  1430. ### use $auth_obj as a persistant version, so we don't have to recreate
  1431. ### modules all the time
  1432. {   my $auth_obj; 
  1433.  
  1434.     sub __create_custom_module_entries {
  1435.         my $self    = shift;
  1436.         my $conf    = $self->configure_object;
  1437.         my %hash    = @_;
  1438.         
  1439.         my $verbose;
  1440.         my $tmpl = {
  1441.             verbose     => { default => $conf->get_conf('verbose'), store => \$verbose },
  1442.         };
  1443.     
  1444.         check( $tmpl, \%hash ) or return undef;
  1445.         
  1446.         my %files = $self->__list_custom_module_sources;     
  1447.     
  1448.         while( my($file,$name) = each %files ) {
  1449.             
  1450.             msg(loc("Adding packages from custom source '%1'", $name), $verbose);
  1451.     
  1452.             my $fh = OPEN_FILE->( $file ) or next;
  1453.     
  1454.             while( <$fh> ) {
  1455.                 chomp;
  1456.                 next if /^#/;
  1457.                 next unless /\S+/;
  1458.                 
  1459.                 ### join on / -- it's a URI after all!
  1460.                 my $parse = join '/', $name, $_;
  1461.     
  1462.                 ### try to make a module object out of it
  1463.                 my $mod = $self->parse_module( module => $parse ) or (
  1464.                     error(loc("Could not parse '%1'", $_)),
  1465.                     next
  1466.                 );
  1467.                 
  1468.                 ### mark this object with a custom author
  1469.                 $auth_obj ||= do {
  1470.                     my $id = CUSTOM_AUTHOR_ID;
  1471.                     
  1472.                     ### if the object is being created for the first time,
  1473.                     ### make sure there's an entry in the author tree as
  1474.                     ### well, so we can search on the CPAN ID
  1475.                     $self->author_tree->{ $id } = 
  1476.                         CPANPLUS::Module::Author::Fake->new( cpanid => $id );          
  1477.                 };
  1478.                 
  1479.                 $mod->author( $auth_obj );
  1480.                 
  1481.                 ### and now add it to the modlue tree -- this MAY
  1482.                 ### override things of course
  1483.                 if( my $old_mod = $self->module_tree( $mod->module ) ) {
  1484.  
  1485.                     ### On VMS use the old module name to get the real case
  1486.                     $mod->module( $old_mod->module ) if ON_VMS;
  1487.  
  1488.                     msg(loc("About to overwrite module tree entry for '%1' with '%2'",
  1489.                             $mod->module, $mod->package), $verbose);
  1490.                 }
  1491.                 
  1492.                 ### mark where it came from
  1493.                 $mod->description( loc("Custom source from '%1'",$name) );
  1494.                 
  1495.                 ### store it in the module tree
  1496.                 $self->module_tree->{ $mod->module } = $mod;
  1497.             }
  1498.         }
  1499.         
  1500.         return 1;
  1501.     }
  1502. }
  1503.  
  1504.  
  1505. # Local variables:
  1506. # c-indentation-style: bsd
  1507. # c-basic-offset: 4
  1508. # indent-tabs-mode: nil
  1509. # End:
  1510. # vim: expandtab shiftwidth=4:
  1511.  
  1512. 1;
  1513.